`; const blob = new Blob([thermalHtml], { type: 'text/html;charset=utf-8' }); const blobUrl = URL.createObjectURL(blob); if (window._printIframe && window._printIframe.parentNode) { try { URL.revokeObjectURL(window._printIframe.dataset.blobUrl); } catch(e) {} window._printIframe.parentNode.removeChild(window._printIframe); window._printIframe = null; } const iframe = document.createElement('iframe'); iframe.style.cssText = 'position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden;'; iframe.dataset.blobUrl = blobUrl; document.body.appendChild(iframe); iframe.onload = function() { setTimeout(function() { try { iframe.contentWindow.focus(); iframe.contentWindow.print(); } catch(e) {} window._printIframe = iframe; setTimeout(function() { try { URL.revokeObjectURL(blobUrl); } catch(e) {} }, 60000); }, 300); }; iframe.src = blobUrl; } function loadTransferHistory() { const transfers = getStorage('transfers'); const tbody = document.getElementById('transferHistoryBody'); if (transfers.length === 0) { tbody.innerHTML = 'لا توجد فواتير بيع'; return; } let html = ''; transfers.forEach((inv, i) => { const statusBadge = inv.status === 'جديد' ? '🆕 جديد' : '✓ تم التجهيز'; const readyBtn = inv.status === 'جديد' ? `` : ''; const rowStyle = inv.status === 'جديد' ? 'style="background:#fff3e0;"' : ''; html += ` ${i+1}#${inv.id}${escapeHtml(inv.branch_name)} ${inv.total_amount.toFixed(2)} ${inv.paid_amount.toFixed(2)} ${inv.remaining_amount.toFixed(2)} ${inv.payment_method} ${statusBadge} ${inv.created_at} ${readyBtn} `; }); tbody.innerHTML = html; } function markTransferReady(id) { let transfers = getStorage('transfers'); const idx = transfers.findIndex(t => t.id === id); if (idx !== -1) { transfers[idx].status = 'تم التجهيز'; setStorage('transfers', transfers); loadTransferHistory(); loadOverview(); alert('✅ تم تحديث حالة الطلب إلى: تم التجهيز'); } } async function deleteTransferInvoice(id) { if (!confirm('هل تريد نقل الفاتورة إلى سلة المحذوفات؟\n⚠️ لن يتم إرجاع أي كميات للمخزن. استخدم "مرتجع" إذا أردت إرجاع البضاعة.')) return; let transfers = getStorage('transfers') || []; const inv = transfers.find(t => t.id === id || String(t.id) === String(id)); if (!inv) return; const user = JSON.parse(localStorage.getItem('currentUser') || '{}'); const deleted = getStorage('deletedTransfers') || []; deleted.unshift({ ...JSON.parse(JSON.stringify(inv)), deleted_at: new Date().toISOString(), deleted_by: user.name || 'مدير النظام' }); const remaining = transfers.filter(t => t.id !== id && String(t.id) !== String(id)); try { await setStorageAwait('deletedTransfers', deleted); await setStorageAwait('transfers', remaining); logAudit('sale', '🗑️ نقل فاتورة بيع للمحذوفات: #' + inv.id + ' | العميل: ' + inv.branch_name, 0); loadTransferHistory(); loadDeletedTransfers(); loadOverview(); alert('✅ تم نقل الفاتورة إلى سلة المحذوفات'); } catch (e) { alert('❌ فشل الحذف: ' + (e.message || e)); } } async function deleteAllTransferInvoices() { const transfers = getStorage('transfers') || []; if (!transfers.length) { alert('لا توجد فواتير بيع لحذفها'); return; } if (!confirm('⚠️ سيتم نقل ' + transfers.length + ' فاتورة إلى سلة المحذوفات.\nهل أنت متأكد؟')) return; const user = JSON.parse(localStorage.getItem('currentUser') || '{}'); const deleted = getStorage('deletedTransfers') || []; const now = new Date().toISOString(); transfers.forEach(inv => deleted.unshift({ ...JSON.parse(JSON.stringify(inv)), deleted_at: now, deleted_by: user.name || 'مدير النظام' })); try { await setStorageAwait('deletedTransfers', deleted); await setStorageAwait('transfers', []); logAudit('sale', '🗑️ نقل جميع فواتير البيع للمحذوفات (' + transfers.length + ')', 0); loadTransferHistory(); loadDeletedTransfers(); loadOverview(); alert('✅ تم النقل إلى سلة المحذوفات'); } catch (e) { alert('❌ فشل العملية: ' + (e.message || e)); } } function loadDeletedTransfers() { const filter = document.getElementById('deletedSalesFilter')?.value || 'all'; let retail = getStorage('deletedTransfers') || []; let wholesale = getStorage('deletedWholesaleTransfers') || []; retail = retail.map(x => Object.assign({}, x, { _type: 'تجزئة' })); wholesale = wholesale.map(x => Object.assign({}, x, { _type: 'جملة' })); let list = []; if (filter === 'retail') list = retail; else if (filter === 'wholesale') list = wholesale; else list = retail.concat(wholesale); list.sort((a,b) => String(b.deleted_at||'').localeCompare(String(a.deleted_at||''))); const tbody = document.getElementById('deletedTransfersBody'); if (!tbody) return; if (!list.length) { tbody.innerHTML = 'لا توجد فواتير محذوفة'; return; } let html = ''; list.forEach((inv, i) => { const delDate = inv.deleted_at ? new Date(inv.deleted_at).toLocaleString('ar-EG') : '-'; const sid = String(inv.id).replace(/'/g, ''); html += ` ${i+1}#${escapeHtml(String(inv.id))}${escapeHtml(inv.branch_name || '')} ${(parseFloat(inv.total_amount)||0).toFixed(2)} ${escapeHtml(inv.payment_method || '')}${escapeHtml(inv.created_at || '')} ${delDate}${escapeHtml(inv.deleted_by || '')} `; }); tbody.innerHTML = html; } async function restoreDeletedTransfer(id) { if (!confirm('استعادة الفاتورة #' + id + '؟')) return; let deleted = getStorage('deletedTransfers') || []; const inv = deleted.find(t => t.id === id || String(t.id) === String(id)); if (!inv) return; const transfers = getStorage('transfers') || []; const restored = { ...inv }; delete restored.deleted_at; delete restored.deleted_by; transfers.unshift(restored); deleted = deleted.filter(t => t.id !== id && String(t.id) !== String(id)); try { await setStorageAwait('transfers', transfers); await setStorageAwait('deletedTransfers', deleted); logAudit('sale', '↩️ استعادة فاتورة بيع: #' + id, parseFloat(inv.total_amount)||0); loadTransferHistory(); loadDeletedTransfers(); loadOverview(); alert('✅ تم استعادة الفاتورة'); } catch (e) { alert('❌ فشل الاستعادة: ' + (e.message || e)); } } async function purgeDeletedTransfer(id) { if (!confirm('حذف نهائي للفاتورة #' + id + '؟')) return; let deleted = getStorage('deletedTransfers') || []; deleted = deleted.filter(t => t.id !== id && String(t.id) !== String(id)); try { await setStorageAwait('deletedTransfers', deleted); logAudit('sale', '🗑️ حذف نهائي لفاتورة بيع: #' + id, 0); loadDeletedTransfers(); alert('✅ تم الحذف النهائي'); } catch (e) { alert('❌ فشل الحذف: ' + (e.message || e)); } } async function purgeAllDeletedTransfers() { const deleted = getStorage('deletedTransfers') || []; if (!deleted.length) { alert('السلة فارغة'); return; } if (!confirm('حذف نهائي لكل المحذوفات (' + deleted.length + ')؟')) return; try { await setStorageAwait('deletedTransfers', []); logAudit('sale', '🗑️ تفريغ محذوفات البيع (' + deleted.length + ')', 0); loadDeletedTransfers(); alert('✅ تم التفريغ'); } catch (e) { alert('❌ فشل: ' + (e.message || e)); } } // ═══════════════════════════════════════ // حسابات الآجل // ═══════════════════════════════════════ function loadDebtorsTable() { // حسابات آجلة للجملة أساساً (+ أي متبقي تجزئة قديم إن وُجد) const retail = (getStorage('transfers') || []).filter(t => (parseFloat(t.remaining_amount) || 0) > 0).map(t => Object.assign({}, t, { _src: 'تجزئة' })); const wholesale = (getStorage('wholesaleTransfers') || []).filter(t => (parseFloat(t.remaining_amount) || 0) > 0).map(t => Object.assign({}, t, { _src: 'جملة', branch_name: t.client_name || t.branch_name })); const transfers = wholesale.concat(retail); const tbody = document.getElementById('debtorsTableBody'); if (transfers.length === 0) { tbody.innerHTML = 'لا توجد حسابات آجل مستحقة'; return; } let html = ''; transfers.forEach((inv, i) => { html += ` ${i+1}#${inv.id}${escapeHtml(inv.branch_name)} ${inv.total_amount.toFixed(2)} ج.م ${inv.paid_amount.toFixed(2)} ج.م ${inv.remaining_amount.toFixed(2)} ج.م ${inv.created_at} `; }); tbody.innerHTML = html; } function payDebt(id) { let transfers = getStorage('transfers'); const inv = transfers.find(t => t.id === id); if (!inv) return; const amount = prompt(`المبلغ المتبقي علي الفاتورة ${inv.remaining_amount} ج.م. أدخل المبلغ المراد سداده:`, inv.remaining_amount); if (amount === null) return; const paid = parseFloat(amount); if (isNaN(paid) || paid <= 0) { alert('مبلغ غير صحيح'); return; } inv.paid_amount += paid; inv.remaining_amount -= paid; if (inv.remaining_amount <= 0) { inv.remaining_amount = 0; inv.payment_method = 'آجل (تم السداد)'; } setStorage('transfers', transfers); logAudit('sale', '💰 سداد جزئي/كلي على فاتورة: ' + inv.id + ' | العميل: ' + inv.branch_name + ' | المبلغ المدفوع: ' + paid.toFixed(2) + ' ج.م | المتبقي: ' + inv.remaining_amount.toFixed(2) + ' ج.م', paid); loadDebtorsTable(); alert('✅ تم تسجيل السداد بنجاح'); } function loadProfitReport() { const transfers = getStorage('transfers'); const products = getStorage('products'); const fromDate = document.getElementById('profitFromDate').value; const toDate = document.getElementById('profitToDate').value; let filtered = transfers; if (fromDate) { filtered = filtered.filter(t => t.created_at >= fromDate); } if (toDate) { filtered = filtered.filter(t => t.created_at <= toDate); } let rev = 0, totalCost = 0; let lossInvoices = 0; const tbody = document.getElementById('profitInvoicesTableBody'); tbody.innerHTML = ''; if (filtered.length === 0) { tbody.innerHTML = 'لا توجد فواتير في الفترة المحددة'; document.getElementById('profitRevenue').innerText = '0 ج.م'; document.getElementById('profitRevenue').style.color = '#1a237e'; document.getElementById('profitCost').innerText = '0 ج.م'; document.getElementById('profitCost').style.color = '#e65100'; document.getElementById('profitNet').innerText = '0 ج.م'; document.getElementById('profitNet').style.color = '#2e7d32'; document.getElementById('profitInvoiceCount').innerText = '0'; const oldSummary = document.getElementById('profitExtraStats'); if (oldSummary) oldSummary.remove(); return; } filtered.forEach((t, idx) => { let invoiceCost = 0; let hasMissingCost = false; let invoiceRevenue = 0; if (t.items && t.items.length > 0) { t.items.forEach(item => { const itemQty = parseFloat(item.quantity) || 0; const price = parseFloat(item.price) || 0; invoiceRevenue += itemQty * price; if (item.costDetails && item.costDetails.length > 0) { item.costDetails.forEach(cd => { invoiceCost += (parseFloat(cd.qty) || 0) * (parseFloat(cd.cost) || 0); }); } else if (item.totalCost) { invoiceCost += parseFloat(item.totalCost) || 0; } else { hasMissingCost = true; } }); } else { invoiceRevenue = t.total_amount || 0; } if (invoiceCost === 0 && !hasMissingCost) { if (t.items && t.items.length > 0) { t.items.forEach(item => { const product = products.find(p => p.id == item.id || p.name === item.name); if (product && product.cost_price) { const qty = parseFloat(item.quantity) || 0; invoiceCost += qty * parseFloat(product.cost_price); } }); } } const invoiceProfit = invoiceRevenue - invoiceCost; const profitPercent = invoiceCost > 0 ? ((invoiceProfit / invoiceCost) * 100).toFixed(1) : 0; if (invoiceProfit < 0) lossInvoices++; rev += invoiceRevenue; totalCost += invoiceCost; const isLoss = invoiceProfit < 0; const profitClass = isLoss ? 'text-danger' : 'text-success'; const profitIcon = isLoss ? '▼ خسارة' : '▲ ربح'; const rowBg = isLoss ? 'style="background:#ffebee;"' : ''; const missingNote = hasMissingCost ? '(بيانات تكلفة غير كاملة)' : ''; tbody.innerHTML += ` ${idx+1} #${t.id} ${escapeHtml(t.branch_name)} ${t.created_at} ${escapeHtml(t.payment_method)} ${invoiceRevenue.toFixed(2)} ج.م ${invoiceCost.toFixed(2)} ج.م ${profitIcon} ${Math.abs(invoiceProfit).toFixed(2)} ج.م${missingNote} ${profitPercent}% `; }); const netProfit = rev - totalCost; const isNetLoss = netProfit < 0; document.getElementById('profitRevenue').innerText = rev.toFixed(2) + ' ج.م'; document.getElementById('profitRevenue').style.color = '#1a237e'; document.getElementById('profitCost').innerText = totalCost.toFixed(2) + ' ج.م'; document.getElementById('profitCost').style.color = '#e65100'; const netEl = document.getElementById('profitNet'); netEl.innerText = (isNetLoss ? '-' : '') + Math.abs(netProfit).toFixed(2) + ' ج.م'; netEl.style.color = isNetLoss ? '#c62828' : '#2e7d32'; netEl.style.fontWeight = 'bold'; document.getElementById('profitInvoiceCount').innerText = filtered.length; // إزالة أي ملخص إضافي قديم const oldSummary = document.getElementById('profitExtraStats'); if (oldSummary) oldSummary.remove(); } function resetProfitDates() { document.getElementById('profitFromDate').value = ''; document.getElementById('profitToDate').value = ''; dpUpdate('profitFromDate', 'dp1'); dpUpdate('profitToDate', 'dp2'); loadProfitReport(); } function loadStockReport() { const products = getStorage('products'); const tbody = document.getElementById('stockTableBody'); let html = ''; products.forEach((p, i) => { const totalQty = p.batches ? p.batches.reduce((sum, b) => sum + (parseFloat(b.qty) || 0), 0) : (parseFloat(p.quantity) || 0); const avgCost = totalQty > 0 && p.batches ? (p.batches.reduce((sum, b) => sum + ((parseFloat(b.cost) || 0) * (parseFloat(b.qty) || 0)), 0) / totalQty).toFixed(2) : (p.cost_price || 0); html += ` ${i+1} ${escapeHtml(p.name)} ${totalQty} ${p.unit} ${avgCost} ج.م ${p.price} ج.م ${(avgCost * totalQty).toFixed(2)} ج.م `; }); tbody.innerHTML = html; } function loadReportProductDropdown() { const products = getStorage('products'); const select = document.getElementById('reportProductSelect'); select.innerHTML = ''; products.forEach(p => { select.innerHTML += ``; }); } // ═══════════════════════════════════════ // دالة مساعدة: تحسب الكمية الفعلية من batches أو quantity // ═══════════════════════════════════════ function getProductStock(product) { if (product.batches && Array.isArray(product.batches) && product.batches.length > 0) { return product.batches.reduce((sum, b) => sum + (parseFloat(b.qty) || 0), 0); } return parseFloat(product.quantity) || 0; } function syncProductQuantity(product) { // تحديث quantity ليكون متزامن مع batches // ملاحظة: opening_quantity ثابت ولا يتغير أبداً if (product.batches && Array.isArray(product.batches)) { product.quantity = product.batches.reduce((sum, b) => sum + (parseFloat(b.qty) || 0), 0); } } function generateItemMovementReport() { const prodId = document.getElementById('reportProductSelect').value; const detailsDiv = document.getElementById('itemReportDetails'); if (!prodId) { detailsDiv.style.display = 'none'; return; } const products = getStorage('products'); const purchases = getStorage('purchases'); const transfers = getStorage('transfers'); const product = products.find(p => p.id == prodId); if (!product) return; let totalIn = 0; purchases.forEach(inv => { inv.items.forEach(item => { if (item.barcode === product.barcode || item.name === product.name) { totalIn += parseFloat(item.quantity) || 0; } }); }); let totalOut = 0; transfers.forEach(inv => { inv.items.forEach(item => { if (item.id == product.id || item.name === product.name) { totalOut += parseFloat(item.quantity) || 0; } }); }); const currentStock = getProductStock(product); let openingQty = parseFloat(product.opening_quantity) || 0; if (!product.opening_quantity) { openingQty = currentStock - totalIn + totalOut; if (openingQty < 0) openingQty = 0; product.opening_quantity = openingQty; let allProducts = getStorage('products'); const pIdx = allProducts.findIndex(p => p.id == prodId); if (pIdx !== -1) { allProducts[pIdx].opening_quantity = openingQty; setStorage('products', allProducts); } } const isSynced = Math.abs((openingQty + totalIn - totalOut) - currentStock) <= 0.01; detailsDiv.style.display = 'block'; detailsDiv.innerHTML = `

تقرير تفصيلي لحركة الصنف: ${escapeHtml(product.name)}

الكمية الافتتاحية
${openingQty} ${product.unit}
إجمالي الوارد (مشتريات)
${totalIn} ${product.unit}
إجمالي المنصرف (مبيعات)
${totalOut} ${product.unit}
الرصيد الحالي بالمخزن
${currentStock} ${product.unit}
التحقق: ${openingQty} افتتاحي + ${totalIn} وارد - ${totalOut} منصرف = ${(openingQty + totalIn - totalOut).toFixed(2)} ${product.unit} ${!isSynced ? ' (⚠️ يوجد فارق!)' : ' (✓ متزامن)'}

الباركود: ${escapeHtml(product.barcode)} | سعر الشراء الحالي: ${product.cost_price || 0} ج.م | سعر البيع: ${product.price} ج.م

`; } function previewLogo(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { uploadedLogoBase64 = e.target.result; const img = document.getElementById('logoPreviewImg'); img.src = uploadedLogoBase64; img.style.display = 'block'; }; reader.readAsDataURL(file); } } function fillAdminSettingsForm() { const settings = getStorage('adminSettings') || {}; const set = (id, v) => { const el = document.getElementById(id); if (el && v != null) el.value = v; }; set('settingCompanyName', settings.companyName || ''); set('settingMainBranch', settings.mainBranch || ''); set('settingPhones', settings.phones || ''); // SMS recovery disabled if (settings.logo) { const img = document.getElementById('logoPreviewImg'); if (img) { img.src = settings.logo; img.style.display = 'block'; } if (typeof uploadedLogoBase64 !== 'undefined') uploadedLogoBase64 = settings.logo; } } function saveAdminSettings() { const prev = getStorage('adminSettings') || {}; const settings = { companyName: document.getElementById('settingCompanyName').value, mainBranch: document.getElementById('settingMainBranch').value, phones: document.getElementById('settingPhones').value, logo: uploadedLogoBase64 || prev.logo || '', smsProvider: prev.smsProvider || 'none', twilioSid: prev.twilioSid || '', twilioToken: prev.twilioToken || '', twilioFrom: prev.twilioFrom || '', smsTemplate: prev.smsTemplate || '' }; setStorage('adminSettings', settings); logAudit('user', '⚙️ تحديث إعدادات المنشأة: ' + (settings.companyName || 'سوبرماركت البطل'), 0); alert('✅ تم حفظ الإعدادات والشعار بنجاح'); } function dpUpdate(id, pickerId) { /* datepicker sync placeholder */ } // ═══════════════════════════════════════ // 💾 نسخ احتياطي واستعادة (من السيرفر + تحميل ملف) // ═══════════════════════════════════════ async function exportBackup() { try { // أولاً: من السيرفر let backupData = {}; try { const serverRes = await apiExportBackup(); backupData = serverRes.backup || serverRes; } catch (_) { // fallback من الذاكرة المحلية API_DATA_KEYS.forEach(key => { const val = getStorage(key); if (val !== undefined) backupData[key] = val; }); } backupData._exportedAt = new Date().toISOString(); backupData._version = 1; const blob = new Blob([JSON.stringify(backupData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'backup_البطل_' + new Date().toISOString().split('T')[0] + '.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); logAudit('user', '💾 تصدير نسخة احتياطية للبيانات', 0); alert('✅ تم تصدير النسخة الاحتياطية بنجاح!'); } catch (err) { alert('❌ فشل التصدير: ' + (err.message || err)); } } function importBackup(input) { const file = input.files[0]; if (!file) return; if (!confirm('⚠️ تحذير: استيراد النسخة الاحتياطية سيحلّ محل جميع البيانات الحالية على السيرفر.\nهل أنت متأكد من المتابعة؟')) { input.value = ''; return; } const reader = new FileReader(); reader.onload = async function(e) { try { const data = JSON.parse(e.target.result); // دعم ملفات localStorage القديمة (قيم نصية) والجديدة (كائنات) const normalized = {}; const keys = Object.keys(data).filter(k => !k.startsWith('_')); keys.forEach(key => { let value = data[key]; if (typeof value === 'string') { try { value = JSON.parse(value); } catch (_) {} } normalized[key] = value; }); if (normalized.products === undefined && normalized.categories === undefined) { alert('❌ ملف النسخة الاحتياطية غير صالح أو تالف!'); input.value = ''; return; } // رفع للسيرفر try { await apiImportBackup({ backup: normalized }); } catch (apiErr) { console.warn('فشل الرفع للسيرفر، حفظ محلي فقط:', apiErr); } // تحديث الذاكرة المحلية Object.keys(normalized).forEach(key => { if (API_DATA_KEYS.includes(key)) { let val = normalized[key]; if (key === 'systemUsers') val = normalizeUsersFromApi(Array.isArray(val) ? val : [], true); if (key === 'registeredUsers') val = normalizeUsersFromApi(Array.isArray(val) ? val : [], false); window.__store[key] = val; try { localStorage.setItem('cache_' + key, JSON.stringify(val)); } catch (_) {} } }); logAudit('user', '📤 استيراد نسخة احتياطية للبيانات', 0); alert('✅ تم استيراد النسخة الاحتياطية بنجاح! سيتم تحديث الصفحة...'); location.reload(); } catch (err) { alert('❌ حدث خطأ أثناء قراءة الملف: ' + err.message); input.value = ''; } }; reader.readAsText(file); } // ═══════════════════════════════════════ // 🔍 البحث السريع الشامل // ═══════════════════════════════════════ function performGlobalSearch() { const query = document.getElementById('globalSearchInput').value.trim().toLowerCase(); const resultsDiv = document.getElementById('globalSearchResults'); if (!query || query.length < 1) { resultsDiv.style.display = 'none'; resultsDiv.innerHTML = ''; return; } let results = []; const products = getStorage('products'); const transfers = getStorage('transfers'); const purchases = getStorage('purchases'); const suppliers = getStorage('suppliers'); const branches = getStorage('branches'); // Search products products.forEach(p => { if ((p.name && p.name.toLowerCase().includes(query)) || (p.barcode && p.barcode.toLowerCase().includes(query))) { results.push({ type: 'product', icon: '📦', title: p.name, subtitle: 'باركود: ' + (p.barcode || '---') + ' | سعر: ' + p.price + ' ج.م', action: () => { showSection('products'); editProduct(p.id); } }); } }); // Search sales invoices transfers.forEach(t => { if (t.id.toString().includes(query) || (t.branch_name && t.branch_name.toLowerCase().includes(query))) { results.push({ type: 'sale', icon: '🛒', title: 'فاتورة بيع #' + t.id, subtitle: t.branch_name + ' | ' + t.total_amount.toFixed(2) + ' ج.م | ' + t.created_at, action: () => { showSection('transfers'); } }); } }); // Search purchase invoices purchases.forEach(p => { if (p.id.toString().includes(query) || (p.supplier_name && p.supplier_name.toLowerCase().includes(query))) { results.push({ type: 'purchase', icon: '📥', title: 'فاتورة شراء #' + p.id, subtitle: p.supplier_name + ' | ' + p.total_cost.toFixed(2) + ' ج.م | ' + p.created_at, action: () => { showSection('purchases'); } }); } }); // Search suppliers suppliers.forEach(s => { if ((s.name && s.name.toLowerCase().includes(query)) || (s.phone && s.phone.includes(query))) { results.push({ type: 'supplier', icon: '🚚', title: s.name, subtitle: 'مورد | ' + (s.phone || 'لا يوجد رقم'), action: () => { showSection('suppliers'); } }); } }); // Search branches branches.forEach(b => { if ((b.name && b.name.toLowerCase().includes(query)) || (b.clientName && b.clientName.toLowerCase().includes(query))) { results.push({ type: 'branch', icon: '🏪', title: b.name || b.clientName, subtitle: 'فرع / عميل | ' + (b.phone || ''), action: () => { showSection('branches'); } }); } }); // Limit results results = results.slice(0, 12); if (results.length === 0) { resultsDiv.innerHTML = '
لا توجد نتائج
'; } else { resultsDiv.innerHTML = results.map((r, i) => `
${r.icon}
${escapeHtml(r.title)}
${escapeHtml(r.subtitle)}
${r.type === 'product' ? 'منتج' : r.type === 'sale' ? 'بيع' : r.type === 'purchase' ? 'شراء' : r.type === 'supplier' ? 'مورد' : 'فرع'}
`).join(''); // Store results for click handler window._globalSearchResults = results; } resultsDiv.style.display = 'block'; } function globalSearchResultClick(index) { if (window._globalSearchResults && window._globalSearchResults[index]) { window._globalSearchResults[index].action(); document.getElementById('globalSearchResults').style.display = 'none'; document.getElementById('globalSearchInput').value = ''; } } // Hide search results when clicking outside document.addEventListener('click', function(e) { const searchBox = document.getElementById('globalSearchInput'); const resultsDiv = document.getElementById('globalSearchResults'); if (searchBox && resultsDiv && !searchBox.contains(e.target) && !resultsDiv.contains(e.target)) { resultsDiv.style.display = 'none'; } }); function disableAllAutocomplete() { document.querySelectorAll('input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]), textarea, select, form').forEach(el => { el.setAttribute('autocomplete', 'off'); }); // تكرار بعد إنشاء عناصر ديناميكية setTimeout(() => { document.querySelectorAll('input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]), textarea, select, form').forEach(el => { el.setAttribute('autocomplete', 'off'); }); }, 500); } // ═══════════════════════════════════════ // فواتير الجملة // ═══════════════════════════════════════ let wsCart = []; function toggleWsClientFields() { const type = document.getElementById('wsClientType')?.value || 'external'; const branchWrap = document.getElementById('wsBranchWrap'); const nameWrap = document.getElementById('wsClientNameWrap'); if (branchWrap) branchWrap.style.display = type === 'branch' ? 'block' : 'none'; if (nameWrap) nameWrap.style.display = type === 'external' ? 'block' : 'none'; if (type === 'branch') { const sel = document.getElementById('wsBranchSelect'); if (sel) { const branches = getStorage('branches') || []; sel.innerHTML = branches.map(b => ``).join('') || ''; } } } function initWholesaleSection() { wsCart = []; updateWsCartUI(); filterWsProducts(); toggleWsClientFields(); loadWholesaleHistory(); } function filterWsProducts() { const q = (document.getElementById('wsProductSearch')?.value || '').trim().toLowerCase(); const grid = document.getElementById('wsProductsGrid'); if (!grid) return; let products = getStorage('products') || []; if (q) { products = products.filter(p => (p.name || '').toLowerCase().includes(q) || (p.barcode || '').toLowerCase().includes(q) || (p.category_main || '').toLowerCase().includes(q) || (p.category_sub || '').toLowerCase().includes(q) || (p.category_item || '').toLowerCase().includes(q) ); } grid.innerHTML = ''; products.slice(0, 60).forEach(p => { const qty = p.batches ? p.batches.reduce((s,b)=>s+(parseFloat(b.qty)||0),0) : (parseFloat(p.quantity)||0); const wprice = (p.wholesale_price != null && p.wholesale_price > 0) ? p.wholesale_price : p.price; grid.innerHTML += `
${escapeHtml(p.name)}
${wprice} ج.م جملة
متاح: ${qty}
`; }); } function addToWsCart(pid) { const products = getStorage('products') || []; const p = products.find(x => String(x.id) === String(pid)); if (!p) return; const qty = p.batches ? p.batches.reduce((s,b)=>s+(parseFloat(b.qty)||0),0) : (parseFloat(p.quantity)||0); if (qty <= 0) { alert('المنتج غير متوفر'); return; } const wprice = (p.wholesale_price != null && p.wholesale_price > 0) ? p.wholesale_price : p.price; const existing = wsCart.find(i => String(i.id) === String(pid)); if (existing) { if (existing.quantity + 1 > qty) { alert('الكمية غير كافية'); return; } existing.quantity += 1; } else { wsCart.push({ id: p.id, name: p.name, price: parseFloat(wprice)||0, quantity: 1, maxQty: qty }); } updateWsCartUI(); } function updateWsCartUI() { const tbody = document.getElementById('wsCartBody'); const totalEl = document.getElementById('wsTotal'); if (!tbody) return; let total = 0; if (!wsCart.length) { tbody.innerHTML = 'السلة فارغة'; } else { tbody.innerHTML = wsCart.map((item, idx) => { const sub = item.price * item.quantity; total += sub; return ` ${escapeHtml(item.name)} ${item.price} ${sub.toFixed(2)} `; }).join(''); } if (totalEl) totalEl.innerText = total.toFixed(2); } function clearWsCart() { wsCart = []; updateWsCartUI(); } async function saveWholesaleInvoice() { if (!wsCart.length) { alert('السلة فارغة'); return; } const type = document.getElementById('wsClientType')?.value || 'external'; let clientName = '', branchName = '', isExternal = 1; if (type === 'branch') { branchName = document.getElementById('wsBranchSelect')?.value || ''; clientName = branchName; isExternal = 0; if (!branchName) { alert('اختر الفرع'); return; } } else { clientName = (document.getElementById('wsClientName')?.value || '').trim(); if (!clientName) { alert('أدخل اسم عميل الجملة'); return; } } const phone = (document.getElementById('wsClientPhone')?.value || '').trim(); const paymentMethod = document.getElementById('wsPaymentMethod')?.value || 'كاش'; const totalAmount = wsCart.reduce((s,i)=>s+i.price*i.quantity, 0); const paidAmount = paymentMethod === 'آجل' ? 0 : totalAmount; const remainingAmount = totalAmount - paidAmount; // خصم المخزون let products = JSON.parse(JSON.stringify(getStorage('products') || [])); const items = []; for (const ci of wsCart) { const prod = products.find(p => String(p.id) === String(ci.id)); if (!prod) { alert('منتج غير موجود: ' + ci.name); return; } if (!prod.batches) prod.batches = []; let need = ci.quantity; let costSum = 0, taken = 0; const available = prod.batches.reduce((s,b)=>s+(parseFloat(b.qty)||0),0) || (parseFloat(prod.quantity)||0); if (need > available + 1e-9) { alert('كمية غير كافية: ' + ci.name); return; } if (prod.batches.length) { for (let b of prod.batches) { if (need <= 0) break; const bq = parseFloat(b.qty)||0; if (bq <= 0) continue; const take = Math.min(bq, need); costSum += take * (parseFloat(b.cost)||0); b.qty = bq - take; need -= take; taken += take; } prod.quantity = prod.batches.reduce((s,b)=>s+(parseFloat(b.qty)||0),0); } else { prod.quantity = (parseFloat(prod.quantity)||0) - ci.quantity; taken = ci.quantity; costSum = taken * (parseFloat(prod.cost_price)||0); } items.push({ id: ci.id, name: ci.name, price: ci.price, quantity: ci.quantity, cost: taken > 0 ? costSum/taken : (parseFloat(prod.cost_price)||0), total: ci.price * ci.quantity }); } const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}'); const inv = { id: 'WSALE-' + Date.now(), client_name: clientName, client_phone: phone, branch_name: branchName, is_external: !!isExternal, total_amount: totalAmount, paid_amount: paidAmount, remaining_amount: remainingAmount, payment_method: paymentMethod, created_at: new Date().toISOString().split('T')[0], user_name: currentUser.name || currentUser.identifier || 'admin', status: 'مكتمل', items }; let list = getStorage('wholesaleTransfers') || []; list = [inv].concat(list); await setStorageAwait('products', products); await setStorageAwait('wholesaleTransfers', list); try { logAudit('sale', 'فاتورة جملة: ' + inv.id + ' — ' + clientName, totalAmount); } catch(_){} alert('✅ تم حفظ فاتورة الجملة بنجاح\nرقم: ' + inv.id); clearWsCart(); loadWholesaleHistory(); if (typeof loadOverview === 'function') loadOverview(); } function loadWholesaleHistory() { const tbody = document.getElementById('wsHistoryBody'); if (!tbody) return; const q = (document.getElementById('wsHistorySearch')?.value || '').trim().toLowerCase(); let list = getStorage('wholesaleTransfers') || []; if (q) list = list.filter(t => (t.id||'').toLowerCase().includes(q) || (t.client_name||'').toLowerCase().includes(q) || (t.branch_name||'').toLowerCase().includes(q) ); if (!list.length) { tbody.innerHTML = 'لا توجد فواتير جملة'; return; } tbody.innerHTML = list.map((t,i) => ` ${i+1} ${escapeHtml(t.id)} ${escapeHtml(t.client_name || t.branch_name || '')} ${(parseFloat(t.total_amount)||0).toFixed(2)} ${(parseFloat(t.paid_amount)||0).toFixed(2)} ${(parseFloat(t.remaining_amount)||0).toFixed(2)} ${escapeHtml(t.created_at||'')} ${escapeHtml(t.status||'')} `).join(''); } async function deleteWholesaleInvoice(id) { if (!confirm('حذف فاتورة الجملة ونقلها للمحذوفات؟')) return; let list = getStorage('wholesaleTransfers') || []; const inv = list.find(t => t.id === id); if (!inv) return; list = list.filter(t => t.id !== id); let deleted = getStorage('deletedWholesaleTransfers') || []; deleted.unshift(Object.assign({}, inv, { deleted_at: new Date().toISOString(), status: 'محذوف' })); await setStorageAwait('wholesaleTransfers', list); await setStorageAwait('deletedWholesaleTransfers', deleted); loadWholesaleHistory(); alert('تم النقل لسلة المحذوفات'); } function printWholesaleInvoice(id) { const list = getStorage('wholesaleTransfers') || []; const t = list.find(x => x.id === id); if (!t) return; const items = (t.items||[]).map(i => `${i.name}${i.quantity}${i.price}${(i.total||i.price*i.quantity).toFixed(2)}`).join(''); const w = window.open('', '_blank', 'width=800,height=900'); w.document.write(`فاتورة جملة ${t.id}

فاتورة بيع جملة — سوبر ماركت البطل

رقم: ${t.id} | التاريخ: ${t.created_at||''}

العميل: ${t.client_name||t.branch_name||''} | الهاتف: ${t.client_phone||'-'}

الدفع: ${t.payment_method||''} | الإجمالي: ${(t.total_amount||0).toFixed(2)} ج.م

${items}
الصنفالكميةالسعرالإجمالي
`); w.document.close(); } window.onload = async function() { initDarkMode(); disableAllAutocomplete(); // تحميل كل البيانات من السيرفر قبل فتح الواجهة try { await loadAllFromServer(); } catch (e) { console.warn('loadAllFromServer failed', e); } checkAuth(); initCharts(); initOrderNotifications(); disableAllAutocomplete(); }; // ═══════════════════════════════════════ // دوال مرتجع فواتير الشراء // ═══════════════════════════════════════ let currentPurchaseReturnInvoice = null; let purchaseReturnItems = []; function openPurchaseReturnModal(id) { const purchases = getStorage('purchases'); const inv = purchases.find(p => p.id === id); if (!inv) return; currentPurchaseReturnInvoice = inv; purchaseReturnItems = JSON.parse(JSON.stringify(inv.items || [])); purchaseReturnItems.forEach(item => { item.returnQty = parseFloat(item.quantity) || 0; }); document.getElementById('purchaseReturnInvoiceId').innerText = inv.id; document.getElementById('purchaseReturnSupplierName').innerText = inv.supplier_name || ''; document.getElementById('purchaseReturnInvoiceDate').innerText = inv.created_at || ''; updatePurchaseReturnModalUI(); document.getElementById('purchaseReturnModal').classList.add('active'); } function closePurchaseReturnModal() { document.getElementById('purchaseReturnModal').classList.remove('active'); currentPurchaseReturnInvoice = null; purchaseReturnItems = []; } function updatePurchaseReturnModalUI() { const tbody = document.getElementById('purchaseReturnItemsBody'); let total = 0; if (purchaseReturnItems.length === 0) { tbody.innerHTML = 'لا توجد أصناف في الفاتورة'; document.getElementById('purchaseReturnTotal').innerText = '0.00'; return; } let html = ''; purchaseReturnItems.forEach((item, index) => { const boughtQty = parseFloat(item.quantity) || 0; const cost = parseFloat(item.costPrice) || 0; const retQty = parseFloat(item.returnQty) || 0; const itemTotal = retQty * cost; total += itemTotal; html += ` ${escapeHtml(item.name)} ${boughtQty} ${escapeHtml(item.unit || 'قطعة')} ${cost.toFixed(2)} ج.م ${itemTotal.toFixed(2)} ج.م `; }); tbody.innerHTML = html; document.getElementById('purchaseReturnTotal').innerText = total.toFixed(2); } function setPurchaseReturnQty(index, val) { const q = parseFloat(val) || 0; const maxQty = parseFloat(purchaseReturnItems[index].quantity) || 0; if (q < 0) { purchaseReturnItems[index].returnQty = 0; } else if (q > maxQty) { alert(`الكمية المراد إرجاعها لا يمكن أن تتجاوز المشتراة (${maxQty})`); purchaseReturnItems[index].returnQty = maxQty; } else { purchaseReturnItems[index].returnQty = q; } updatePurchaseReturnModalUI(); } function savePurchaseReturn() { if (!currentPurchaseReturnInvoice) return; const itemsToReturn = purchaseReturnItems.filter(item => (parseFloat(item.returnQty) || 0) > 0); if (itemsToReturn.length === 0) { alert('يرجى إدخال كمية مراد إرجاعها لصنف واحد على الأقل'); return; } const returnTotal = itemsToReturn.reduce((sum, item) => sum + ((parseFloat(item.returnQty) || 0) * (parseFloat(item.costPrice) || 0)), 0); if (returnTotal <= 0) { alert('قيمة المرتجع غير صالحة'); return; } // ✅ التحقق من توفر الكميات في المخزن قبل المرتجع let products = getStorage('products'); let stockErrors = []; itemsToReturn.forEach(retItem => { const retQty = parseFloat(retItem.returnQty) || 0; const product = products.find(p => p.barcode === retItem.barcode || p.name === retItem.name); if (!product) { stockErrors.push(`❌ الصنف "${retItem.name}" غير موجود في المخزن`); return; } const availableQty = product.batches ? product.batches.reduce((sum, b) => sum + (parseFloat(b.qty) || 0), 0) : 0; if (availableQty < retQty) { stockErrors.push(`❌ "${retItem.name}": متاح ${availableQty} | مطلوب إرجاع ${retQty}`); } }); if (stockErrors.length > 0) { alert('⚠️ لا يمكن تنفيذ المرتجع:\n\n' + stockErrors.join('\n') + '\n\nالكمية المراد إرجاعها تتجاوز المخزون المتاح.'); return; } if (!confirm(`تأكيد تسجيل مرتجع بقيمة ${returnTotal.toFixed(2)} ج.م؟\nسيتم خصم الكميات من المخزن.`)) return; let purchases = getStorage('purchases'); const invIndex = purchases.findIndex(p => p.id === currentPurchaseReturnInvoice.id); if (invIndex === -1) { alert('الفاتورة غير موجودة'); return; } const inv = purchases[invIndex]; // 1. Deduct quantities from stock itemsToReturn.forEach(retItem => { const retQty = parseFloat(retItem.returnQty) || 0; const product = products.find(p => p.barcode === retItem.barcode || p.name === retItem.name); if (product && product.batches) { let remaining = retQty; for (let i = 0; i < product.batches.length && remaining > 0; i++) { const batch = product.batches[i]; const batchCost = parseFloat(batch.cost) || 0; const itemCost = parseFloat(retItem.costPrice) || 0; // Match by cost (same batch) if (Math.abs(batchCost - itemCost) < 0.01) { const deduct = Math.min(remaining, parseFloat(batch.qty) || 0); batch.qty = (parseFloat(batch.qty) || 0) - deduct; remaining -= deduct; } } // If still remaining, deduct from any batch if (remaining > 0) { for (let i = 0; i < product.batches.length && remaining > 0; i++) { const batch = product.batches[i]; const deduct = Math.min(remaining, parseFloat(batch.qty) || 0); batch.qty = (parseFloat(batch.qty) || 0) - deduct; remaining -= deduct; } } product.batches = product.batches.filter(b => (parseFloat(b.qty) || 0) > 0); syncProductQuantity(product); } }); // 2. Update original purchase invoice if (!inv.returns) inv.returns = []; const returnRecord = { returnId: 'PUR-RET-' + Date.now().toString().slice(-6), date: getLocalDateString(), items: itemsToReturn.map(item => ({ name: item.name, quantity: parseFloat(item.returnQty) || 0, costPrice: parseFloat(item.costPrice) || 0, unit: item.unit })), returnTotal: returnTotal }; inv.returns.push(returnRecord); // Update invoice totals inv.total_cost -= returnTotal; if (inv.total_cost < 0) inv.total_cost = 0; // Update items quantities in the original invoice itemsToReturn.forEach(retItem => { const originalItem = inv.items.find(it => it.name === retItem.name); if (originalItem) { originalItem.quantity = (parseFloat(originalItem.quantity) || 0) - (parseFloat(retItem.returnQty) || 0); if (originalItem.quantity <= 0) originalItem.quantity = 0; } }); setStorage('products', products); setStorage('purchases', purchases); logAudit('return', `↩️ مرتجع على فاتورة شراء #${inv.id} | المورد: ${inv.supplier_name} | قيمة المرتجع: ${returnTotal.toFixed(2)} ج.م`, returnTotal); closePurchaseReturnModal(); loadPurchaseHistory(); loadOverview(); alert(`✅ تم تسجيل مرتجع الشراء بنجاح!\nرقم المرتجع: ${returnRecord.returnId}\nالقيمة: ${returnTotal.toFixed(2)} ج.م`); } // ====== تعديل فواتير الشراء ====== let editingPurchaseId = null; function editPurchaseInvoice(id) { const purchases = getStorage('purchases'); const inv = purchases.find(p => p.id === id); if (!inv) return; editingPurchaseId = id; purchaseCart = JSON.parse(JSON.stringify(inv.items || [])); document.getElementById('purchaseSupplier').value = inv.supplier_name || ''; document.getElementById('purchaseSupplierPhone').value = inv.supplier_phone || ''; // Update modal title const modalTitle = document.querySelector('#purchaseModal h3'); if (modalTitle) modalTitle.innerHTML = ' تعديل فاتورة شراء #' + inv.id; updatePurchaseCartUI(); document.getElementById('purchaseModal').classList.add('active'); } function savePurchaseInvoice() { if (purchaseCart.length === 0) { alert('أضف أصناف للفاتورة'); return; } for (let item of purchaseCart) { if (!item.quantity || parseFloat(item.quantity) <= 0) { alert('يرجى التأكد من إدخال كميات صحيحة لجميع الأصناف'); return; } } const supplier = document.getElementById('purchaseSupplier').value.trim() || 'مورد عام'; const supplierPhone = document.getElementById('purchaseSupplierPhone').value.trim(); const supplierAddressEl = document.getElementById('purchaseSupplierAddress'); const supplierAddress = supplierAddressEl ? supplierAddressEl.value.trim() : ''; const totalCost = purchaseCart.reduce((sum, i) => sum + ((parseFloat(i.costPrice) || 0) * (parseFloat(i.quantity) || 0)), 0); const invDate = getLocalDateString(); // حفظ/تحديث المورد في قاعدة بيانات الموردين if (supplier && supplier !== 'مورد عام') { let suppliers = getStorage('suppliers') || []; if (!Array.isArray(suppliers)) suppliers = []; const nameKey = supplier.toLowerCase(); const existingIdx = suppliers.findIndex(s => (s.name || '').trim().toLowerCase() === nameKey); if (existingIdx !== -1) { if (supplierPhone) suppliers[existingIdx].phone = supplierPhone; if (supplierAddress) suppliers[existingIdx].address = supplierAddress; } else { suppliers.push({ id: Date.now(), name: supplier, phone: supplierPhone || '', address: supplierAddress || '' }); } setStorage('suppliers', suppliers); if (typeof loadSuppliersTable === 'function') { try { loadSuppliersTable(); } catch (_) {} } } let purchases = getStorage('purchases'); let products = getStorage('products'); if (editingPurchaseId) { // ====== وضع التعديل ====== const oldInv = purchases.find(p => p.id === editingPurchaseId); if (oldInv && oldInv.items) { oldInv.items.forEach(oldItem => { const p = products.find(prod => prod.barcode === oldItem.barcode || prod.name === oldItem.name); if (p && p.batches) { let remaining = parseFloat(oldItem.quantity) || 0; for (let b of p.batches) { if (remaining <= 0) break; if (Math.abs(b.cost - (parseFloat(oldItem.costPrice) || 0)) < 0.01) { let deduct = Math.min(remaining, b.qty); b.qty -= deduct; remaining -= deduct; } } for (let b of p.batches) { if (remaining <= 0) break; let deduct = Math.min(remaining, b.qty); b.qty -= deduct; remaining -= deduct; } p.batches = p.batches.filter(b => b.qty > 0); } }); } const idx = purchases.findIndex(p => p.id === editingPurchaseId); if (idx !== -1) { purchases[idx] = { id: editingPurchaseId, supplier_name: supplier, supplier_phone: supplierPhone, items: JSON.parse(JSON.stringify(purchaseCart)), total_cost: totalCost, created_at: oldInv.created_at, user_name: oldInv.user_name }; } editingPurchaseId = null; } else { purchases.push({ id: Date.now(), supplier_name: supplier, supplier_phone: supplierPhone, items: JSON.parse(JSON.stringify(purchaseCart)), total_cost: totalCost, created_at: invDate, user_name: (JSON.parse(localStorage.getItem('currentUser') || '{}').name || 'مدير النظام') }); } // إضافة دفعات جديدة للمخزن + تحديث quantity purchaseCart.forEach(item => { const qVal = parseFloat(item.quantity) || 0; const cVal = parseFloat(item.costPrice) || 0; const sVal = parseFloat(item.sellPrice) || 0; const idx = products.findIndex(p => p.barcode === item.barcode || p.name === item.name); if (idx !== -1) { if (!products[idx].batches) products[idx].batches = []; products[idx].batches.push({ qty: qVal, cost: cVal, date: invDate }); if (sVal > 0) products[idx].price = sVal; if (item.category) products[idx].category = item.category; // تحديث quantity ليكون متزامن مع batches syncProductQuantity(products[idx]); } else { const newProduct = { id: Date.now(), name: item.name || 'صنف جديد', category: item.category || 'عام', barcode: item.barcode || Math.floor(Math.random() * 1000000000).toString(), unit: item.unit || 'قطعة', min_quantity: 5, price: sVal > 0 ? sVal : cVal * 1.3, image_url: 'https://via.placeholder.com/50', quantity: qVal, batches: [{ qty: qVal, cost: cVal, date: invDate }] }; products.push(newProduct); } }); setStorage('purchases', purchases); setStorage('products', products); const modalTitle = document.querySelector('#purchaseModal h3'); if (modalTitle) modalTitle.innerHTML = ' فاتورة شراء جديدة'; logAudit('purchase', (editingPurchaseId ? '✏️ تعديل فاتورة شراء رقم: #' + editingPurchaseId + ' | المورد: ' + supplier + ' | عدد الأصناف: ' + purchaseCart.length : '➕ إنشاء فاتورة شراء جديدة | المورد: ' + supplier + ' | عدد الأصناف: ' + purchaseCart.length), totalCost); closePurchaseModal(); loadPurchaseHistory(); loadOverview(); alert('✅ تم حفظ فاتورة الشراء وتحديث المخزن بنجاح'); } // ====== تعديل فواتير البيع ====== let editingTransferId = null; function editTransferInvoice(id) { const transfers = getStorage('transfers'); const inv = transfers.find(t => t.id === id); if (!inv) return; editingTransferId = id; originalTransferForEdit = JSON.parse(JSON.stringify(inv)); // نسخة احتياطية transferCart = JSON.parse(JSON.stringify(inv.items || [])); // ✅ إصلاح: تعبئة بيانات المخزن الحالي لكل صنف في وضع التعديل const products = getStorage('products'); transferCart.forEach(item => { const p = products.find(prod => prod.id == item.id || prod.name === item.name); if (p) { item.stock = getProductStock(p); item.unit = item.unit || p.unit || 'قطعة'; item.price = parseFloat(item.price) || parseFloat(p.price) || 0; } else { item.stock = 0; } }); loadBranchesDropdown(); document.getElementById('transferBranch').value = inv.branch_name || ''; document.getElementById('paymentMethod').value = inv.payment_method || 'كاش'; togglePaymentInputs(); if (inv.payment_method === 'آجل') { document.getElementById('paidAmountInput').value = inv.paid_amount || 0; } document.getElementById('transferFormTitle').innerHTML = ' تعديل فاتورة بيع #' + inv.id; updateTransferCartUI(); loadTransferProducts(); document.getElementById('section-transfers').scrollIntoView({ behavior: 'smooth' }); } async function saveTransferInvoice() { const saveBtn = document.getElementById('saveTransferBtn'); if (saveBtn) { if (saveBtn.dataset.busy === '1') return; saveBtn.dataset.busy = '1'; saveBtn.disabled = true; } try { if (!transferCart || transferCart.length === 0) { alert('أضف أصناف للفاتورة'); return; } const branchEl = document.getElementById('transferBranch'); const branchName = (branchEl && branchEl.value) ? branchEl.value.trim() : ''; if (!branchName) { alert('يرجى اختيار الفرع أو العميل المستلم'); return; } const paymentMethodEl = document.getElementById('paymentMethod'); const paymentMethod = (paymentMethodEl && paymentMethodEl.value) ? paymentMethodEl.value : 'كاش'; let products = getStorage('products') || []; if (!Array.isArray(products)) products = []; // التحقق من الكميات والمخزون for (let item of transferCart) { const qVal = parseFloat(item.quantity); if (!qVal || qVal <= 0) { alert('يرجى إدخال كمية صحيحة للصنف: ' + (item.name || '')); return; } const product = products.find(p => p.id == item.id || String(p.id) === String(item.id) || (item.name && p.name === item.name)); if (!product) { alert('المنتج غير موجود في المخزن: ' + (item.name || '')); return; } const availableStock = getProductStock(product); let reservedQty = 0; if (editingTransferId && originalTransferForEdit && originalTransferForEdit.items) { const oldItem = originalTransferForEdit.items.find(oi => oi.id == item.id || String(oi.id) === String(item.id) || oi.name === item.name); if (oldItem) reservedQty = parseFloat(oldItem.quantity) || 0; } if (qVal > (availableStock + reservedQty + 0.0001)) { alert('❌ الكمية غير متوفرة للمنتج: ' + item.name + '\nالمخزون المتاح: ' + (availableStock + reservedQty) + ' | المطلوب: ' + qVal); return; } } let transfers = getStorage('transfers') || []; if (!Array.isArray(transfers)) transfers = []; // لو وضع التعديل: أرجع الكميات القديمة للمخزن أولاً if (editingTransferId && originalTransferForEdit && originalTransferForEdit.items) { originalTransferForEdit.items.forEach(item => { const p = products.find(prod => prod.id == item.id || String(prod.id) === String(item.id) || prod.name === item.name); if (!p) return; if (!p.batches) p.batches = []; if (item.costDetails && item.costDetails.length > 0) { item.costDetails.forEach(cd => { p.batches.push({ qty: parseFloat(cd.qty) || 0, cost: parseFloat(cd.cost) || 0, date: getLocalDateString() }); }); } else { const qty = parseFloat(item.quantity) || 0; if (qty > 0) { const cost = item.totalCost ? (item.totalCost / qty) : (parseFloat(p.cost_price) || 0); p.batches.push({ qty: qty, cost: cost, date: getLocalDateString() }); } } syncProductQuantity(p); }); } // تحضير items مع تفاصيل التكلفة (FIFO) let itemsWithCost = []; for (let item of transferCart) { const product = products.find(p => p.id == item.id || String(p.id) === String(item.id) || (item.name && p.name === item.name)); if (!product) { alert('المنتج غير موجود في المخزن: ' + (item.name || '')); return; } // تنظيف الدفعات الفارغة if (product.batches && Array.isArray(product.batches)) { product.batches = product.batches.filter(b => (parseFloat(b.qty) || 0) > 0); } // إنشاء دفعة افتراضية لو مفيش batches if (!product.batches || product.batches.length === 0) { const qty = parseFloat(product.quantity) || 0; const cost = parseFloat(product.cost_price) || 0; if (qty <= 0) { alert('المنتج نفذ من المخزن: ' + item.name); return; } product.batches = [{ qty: qty, cost: cost, date: getLocalDateString() }]; } let sellQty = parseFloat(item.quantity) || 0; let costDetails = []; let totalItemCost = 0; for (let batch of product.batches) { if (sellQty <= 0) break; const batchQty = parseFloat(batch.qty) || 0; if (batchQty <= 0) continue; const deduct = Math.min(sellQty, batchQty); batch.qty = batchQty - deduct; costDetails.push({ qty: deduct, cost: parseFloat(batch.cost) || 0 }); totalItemCost += deduct * (parseFloat(batch.cost) || 0); sellQty -= deduct; } product.batches = product.batches.filter(b => (parseFloat(b.qty) || 0) > 0); if (sellQty > 0.0001) { alert('❌ الكمية غير متوفرة للمنتج: ' + item.name + '\nتعذر خصم الكمية بالكامل من المخزن.'); return; } itemsWithCost.push({ id: product.id, name: item.name || product.name, quantity: parseFloat(item.quantity) || 0, price: parseFloat(item.price) || parseFloat(product.price) || 0, unit: item.unit || product.unit || 'قطعة', costDetails: costDetails, totalCost: totalItemCost }); } const totalAmount = itemsWithCost.reduce((sum, item) => sum + ((parseFloat(item.price) || 0) * (parseFloat(item.quantity) || 0)), 0); let paidAmount = totalAmount; if (paymentMethod === 'آجل') { const paidEl = document.getElementById('paidAmountInput'); paidAmount = paidEl ? (parseFloat(paidEl.value) || 0) : 0; } const remainingAmount = totalAmount - paidAmount; // مزامنة quantity مع batches itemsWithCost.forEach(item => { const product = products.find(p => p.id == item.id || String(p.id) === String(item.id)); if (product) syncProductQuantity(product); }); if (editingTransferId) { const idx = transfers.findIndex(t => t.id == editingTransferId || String(t.id) === String(editingTransferId)); if (idx !== -1) { transfers[idx] = { id: editingTransferId, branch_name: branchName, items: itemsWithCost, total_amount: totalAmount, paid_amount: paidAmount, remaining_amount: remainingAmount, payment_method: paymentMethod, created_at: transfers[idx].created_at, user_name: (JSON.parse(localStorage.getItem('currentUser') || '{}').name || transfers[idx].user_name || 'مدير النظام'), status: transfers[idx].status || 'جديد' }; } const editIdSnapshot = editingTransferId; editingTransferId = null; originalTransferForEdit = null; await setStorageAwait('products', products); await setStorageAwait('transfers', transfers); logAudit('sale', '✏️ تعديل فاتورة بيع رقم: ' + editIdSnapshot + ' | العميل: ' + branchName + ' | عدد الأصناف: ' + itemsWithCost.length, totalAmount); const titleEl = document.getElementById('transferFormTitle'); if (titleEl) titleEl.innerHTML = ' فاتورة بيع جديدة'; transferCart = []; updateTransferCartUI(); loadTransferProducts(); loadTransferHistory(); loadOverview(); alert('✅ تم تعديل فاتورة البيع بنجاح'); } else { const newTransfer = { id: 'SALE-' + Date.now().toString().slice(-6), branch_name: branchName, items: itemsWithCost, total_amount: totalAmount, paid_amount: paidAmount, remaining_amount: remainingAmount, payment_method: paymentMethod, created_at: getLocalDateString(), user_name: (JSON.parse(localStorage.getItem('currentUser') || '{}').name || 'مدير النظام'), status: 'جديد' }; transfers.push(newTransfer); lastTransferId = newTransfer.id; await setStorageAwait('products', products); await setStorageAwait('transfers', transfers); logAudit('sale', '➕ إنشاء فاتورة بيع جديدة رقم: ' + newTransfer.id + ' | العميل: ' + branchName + ' | عدد الأصناف: ' + itemsWithCost.length + ' | طريقة الدفع: ' + paymentMethod, totalAmount); const invoiceForm = document.getElementById('transferInvoiceForm'); const successBox = document.getElementById('transferSuccessBox'); if (invoiceForm) { invoiceForm.style.display = 'none'; invoiceForm.classList.add('form-hidden'); invoiceForm.classList.remove('form-visible'); } if (successBox) { successBox.style.display = 'block'; successBox.classList.remove('form-hidden'); } transferCart = []; updateTransferCartUI(); loadTransferProducts(); loadTransferHistory(); loadOverview(); } } catch (err) { console.error('saveTransferInvoice error:', err); alert('❌ حدث خطأ أثناء حفظ فاتورة البيع:\n' + (err && err.message ? err.message : String(err))); } finally { if (saveBtn) { saveBtn.dataset.busy = '0'; saveBtn.disabled = false; } } } // ====== بحث وفلترة المنتجات في صفحة الأدمن ====== function loadAdminCategoryFilter() { const categories = normalizeCategories(getStorage('categories')); const select = document.getElementById('adminProductCategoryFilter'); if (!select) return; const currentVal = select.value; select.innerHTML = ''; const leaves = categories.filter(c => c.level === 3); const list = leaves.length > 0 ? leaves : categories; list.forEach(cat => { const label = leaves.length > 0 ? getCategoryPath(cat, categories) : cat.name; select.innerHTML += ``; }); select.value = currentVal; } function filterAdminProducts() { const searchQuery = document.getElementById('adminProductSearch').value.trim().toLowerCase(); const categoryFilter = document.getElementById('adminProductCategoryFilter').value; const allProducts = getStorage('products'); let filtered = allProducts; // Filter by category (أي مستوى) if (categoryFilter) { filtered = filtered.filter(p => p.category === categoryFilter || p.category_main === categoryFilter || p.category_sub === categoryFilter || p.category_item === categoryFilter ); } // Filter by search query (name or barcode) if (searchQuery) { filtered = filtered.filter(p => { const nameMatch = (p.name || '').toLowerCase().includes(searchQuery); const barcodeMatch = (p.barcode || '').toLowerCase().includes(searchQuery); return nameMatch || barcodeMatch; }); } currentProductsList = filtered; sortProductsTable(); // Show result count if filtering const tbody = document.getElementById('productsTableBody'); if (filtered.length === 0 && (searchQuery || categoryFilter)) { tbody.innerHTML = ` لا توجد منتجات تطابق البحث `; } } function resetAdminProductFilter() { document.getElementById('adminProductSearch').value = ''; document.getElementById('adminProductCategoryFilter').value = ''; loadProductsTable(); } // ═══════════════════════════════════════ // 4️⃣ مسح الباركود — السكانر USB + الكاميرا // ═══════════════════════════════════════ let html5QrCode = null; let scannerModeActive = false; // 🔌 وضع السكانر USB // ═══════════════════════════════════════ // 9️⃣ سجل العمليات (Audit Trail) // ═══════════════════════════════════════ function logAudit(type, description, amount) { const logs = getStorage('auditLog'); const user = JSON.parse(localStorage.getItem('currentUser') || '{}'); logs.unshift({ id: Date.now(), date: new Date().toISOString(), user: user.name || 'غير معروف', type: type, description: description, amount: amount || 0 }); // Keep only last 1000 records if (logs.length > 1000) logs.pop(); setStorage('auditLog', logs); } function loadAuditLog() { const tbody = document.getElementById('auditTableBody'); if (!tbody) return; let logs = getStorage('auditLog'); const typeFilter = document.getElementById('auditFilterType')?.value || ''; const fromDate = document.getElementById('auditFilterFrom')?.value || ''; const toDate = document.getElementById('auditFilterTo')?.value || ''; if (typeFilter) { logs = logs.filter(l => l.type === typeFilter); } if (fromDate) { logs = logs.filter(l => (l.localDate || l.date.split('T')[0]) >= fromDate); } if (toDate) { logs = logs.filter(l => (l.localDate || l.date.split('T')[0]) <= toDate); } if (logs.length === 0) { tbody.innerHTML = 'لا يوجد سجلات'; return; } const typeIcons = { sale: '🛒', purchase: '📥', product: '📦', user: '👤', return: '↩️', login: '🔑' }; let html = ''; logs.slice(0, 100).forEach((log, i) => { const dateObj = new Date(log.date); const dateStr = dateObj.toLocaleDateString('ar-EG') + ' ' + dateObj.toLocaleTimeString('ar-EG', {hour:'2-digit', minute:'2-digit'}); let actionIcon = '•'; const desc = log.description || ''; if (desc.includes('➕') || desc.includes('إضافة') || desc.includes('إنشاء')) actionIcon = '➕'; else if (desc.includes('✏️') || desc.includes('تعديل') || desc.includes('تحديث')) actionIcon = '✏️'; else if (desc.includes('🗑️') || desc.includes('حذف') || desc.includes('مسح')) actionIcon = '🗑️'; else if (desc.includes('💰') || desc.includes('سداد') || desc.includes('دفع')) actionIcon = '💰'; else if (desc.includes('📦') || desc.includes('تجهيز') || desc.includes('حالة')) actionIcon = '📦'; else if (desc.includes('⚙️') || desc.includes('إعدادات')) actionIcon = '⚙️'; let cleanDesc = desc.replace(/^[➕✏️🗑️💰📦⚙️]\s*/, '').trim(); html += ` ${i+1} ${dateStr} ${escapeHtml(log.user)} ${typeIcons[log.type] || '•'} ${getAuditTypeText(log.type)} ${actionIcon} ${escapeHtml(cleanDesc)} ${log.amount > 0 ? log.amount.toFixed(2) + ' ج.م' : '-'} `; }); tbody.innerHTML = html; } function getAuditTypeText(type) { const types = { sale: 'عملية بيع', purchase: 'عملية شراء', product: 'إدارة منتج', user: 'إدارة مستخدم', return: 'مرتجع', login: 'تسجيل دخول' }; return types[type] || type; } function clearAuditLog() { if (!confirm('هل تريد مسح جميع السجلات؟')) return; setStorage('auditLog', []); loadAuditLog(); } // ═══════════════════════════════════════ // 5️⃣ الوضع الداكن (Dark Mode) // ═══════════════════════════════════════ function toggleDarkMode() { document.body.classList.toggle('dark-mode'); const isDark = document.body.classList.contains('dark-mode'); localStorage.setItem('darkMode', isDark ? '1' : '0'); const btn = document.querySelector('.dark-mode-toggle'); if (btn) { btn.innerHTML = isDark ? '' : ''; } // ✅ إعادة رسم الرسوم البيانية بالألوان المناسبة setTimeout(() => renderOverviewCharts(), 100); } function disableAllAutocomplete() { document.querySelectorAll('input, textarea, select').forEach(el => { el.setAttribute('autocomplete', 'off'); el.setAttribute('autocorrect', 'off'); el.setAttribute('autocapitalize', 'off'); el.setAttribute('spellcheck', 'false'); if (el.tagName === 'INPUT') { const t = (el.type || 'text').toLowerCase(); if (t === 'text' || t === 'search' || t === 'password' || t === 'email' || t === 'tel' || t === 'number' || t === '') { if (!el.getAttribute('name') || el.getAttribute('name') === '') { el.setAttribute('name', 'no-autofill-' + Math.random().toString(36).slice(2, 8)); } // new-password أقوى ضد اقتراحات المتصفح el.setAttribute('autocomplete', 'new-password'); } } }); } // إعادة تطبيق إيقاف الإكمال التلقائي عند إضافة حقول ديناميكية function watchAutocompleteOff() { if (window.__acObserver) return; window.__acObserver = new MutationObserver(() => { clearTimeout(window.__acTimer); window.__acTimer = setTimeout(disableAllAutocomplete, 150); }); window.__acObserver.observe(document.body, { childList: true, subtree: true }); } function initDarkMode() { if (localStorage.getItem('darkMode') === '1') { document.body.classList.add('dark-mode'); const btn = document.querySelector('.dark-mode-toggle'); if (btn) btn.innerHTML = ''; } } // ═══════════════════════════════════════ // إشعارات الطلبات الجديدة (من صفحة العميل) // ═══════════════════════════════════════ let lastCheckedOrderTime = 0; let notificationAudio = null; function initOrderNotifications() { // Create audio element for notification sound notificationAudio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBTGH0fPTgjMGHm7A7+OZSA0PVanu8LdnHwU2k9n1unEiBC13yO/eizEIHWq+8+OZURE'); // Check immediately on load checkForNewOrders(); // Listen for storage changes from other tabs (index.html) window.addEventListener('storage', function(e) { if (e.key === 'lastOrderTimestamp' || e.key === 'pendingOrderNotifications') { checkForNewOrders(); } }); // Periodic check every 10 seconds setInterval(checkForNewOrders, 10000); } async function checkForNewOrders() { // تحديث من السيرفر إن أمكن try { const remote = await apiGetCollection('pendingOrderNotifications'); if (Array.isArray(remote)) { window.__store['pendingOrderNotifications'] = remote; } } catch (_) {} const pendingNotifications = getStorage('pendingOrderNotifications') || []; const now = Date.now(); const recentNotifications = pendingNotifications.filter(n => { const notifTime = new Date(n.time).getTime(); return (now - notifTime < 300000) && notifTime > lastCheckedOrderTime; }); if (recentNotifications.length > 0) { const latest = recentNotifications[recentNotifications.length - 1]; showOrderNotification(latest); lastCheckedOrderTime = now; } } function showOrderNotification(order) { const notif = document.getElementById('orderNotification'); const text = document.getElementById('orderNotificationText'); text.innerHTML = ` ${escapeHtml(order.customer)} أرسل طلباً بقيمة ${parseFloat(order.total || 0).toFixed(2)} ج.م
الفرع: ${escapeHtml(order.branch || 'غير محدد')} `; // Store current order ID for the view button notif.dataset.orderId = order.orderId || ''; notif.classList.add('show'); // Play sound if (notificationAudio) { notificationAudio.play().catch(e => console.log('Audio play failed:', e)); } // Auto dismiss after 15 seconds setTimeout(() => { dismissOrderNotification(); }, 15000); } function dismissOrderNotification() { const notif = document.getElementById('orderNotification'); notif.classList.remove('show'); } function viewNewOrder() { dismissOrderNotification(); showSection('transfers'); loadTransferHistory(); // Mark notifications as seen setStorage('pendingOrderNotifications', []); lastCheckedOrderTime = Date.now(); } // ═══════════════════════════════════════ // 3️⃣ التقارير البيانية (Charts) // ═══════════════════════════════════════ let salesChart = null; let productsChart = null; window.dailySalesChartInstance = window.dailySalesChartInstance || null; window.topProductsChartInstance = window.topProductsChartInstance || null; function renderOverviewCharts() { const transfers = getStorage('transfers') || []; const wsTransfers = getStorage('wholesaleTransfers') || []; const isDark = document.body.classList.contains('dark-mode'); const textColor = isDark ? '#e0e0e0' : '#333'; const gridColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)'; // آخر 7 أيام const days = []; const retailByDay = {}; const wsByDay = {}; for (let i = 6; i >= 0; i--) { const d = new Date(); d.setDate(d.getDate() - i); const key = d.toISOString().split('T')[0]; days.push(key); retailByDay[key] = 0; wsByDay[key] = 0; } transfers.forEach(t => { const key = (t.created_at || '').substring(0, 10); if (key in retailByDay) retailByDay[key] += parseFloat(t.total_amount) || 0; }); wsTransfers.forEach(t => { const key = (t.created_at || '').substring(0, 10); if (key in wsByDay) wsByDay[key] += parseFloat(t.total_amount) || 0; }); const dayLabels = days.map(k => { const parts = k.split('-'); return parts[2] + '/' + parts[1]; }); // ===== 1. مبيعات يومية تجزئة (موجود) + نضيف جملة في نفس الرسم إن أمكن ===== const dailyCtx = document.getElementById('dailySalesChart'); if (dailyCtx) { if (window.dailySalesChartInstance) window.dailySalesChartInstance.destroy(); window.dailySalesChartInstance = new Chart(dailyCtx, { type: 'line', data: { labels: dayLabels, datasets: [ { label: 'تجزئة', data: days.map(k => retailByDay[k]), borderColor: '#1a237e', backgroundColor: 'rgba(26,35,126,0.12)', tension: 0.35, fill: true }, { label: 'جملة', data: days.map(k => wsByDay[k]), borderColor: '#00897b', backgroundColor: 'rgba(0,137,123,0.12)', tension: 0.35, fill: true } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: textColor } }, tooltip: { callbacks: { label: function(context) { return (context.dataset.label || '') + ': ' + context.parsed.y.toFixed(2) + ' ج.م'; } } } }, scales: { x: { ticks: { color: textColor }, grid: { color: gridColor } }, y: { beginAtZero: true, ticks: { color: textColor, callback: v => v + ' ج.م' }, grid: { color: gridColor } } } } }); } // ===== مقارنة أعمدة ===== const cmpCtx = document.getElementById('compareSalesChart'); if (cmpCtx) { if (window.compareSalesChartInstance) window.compareSalesChartInstance.destroy(); window.compareSalesChartInstance = new Chart(cmpCtx, { type: 'bar', data: { labels: dayLabels, datasets: [ { label: 'تجزئة', data: days.map(k => retailByDay[k]), backgroundColor: '#3949ab' }, { label: 'جملة', data: days.map(k => wsByDay[k]), backgroundColor: '#26a69a' } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: textColor } } }, scales: { x: { ticks: { color: textColor }, grid: { color: gridColor } }, y: { beginAtZero: true, ticks: { color: textColor, callback: v => v + ' ج.م' }, grid: { color: gridColor } } } } }); } function top5(list) { const map = {}; list.forEach(t => { (t.items || []).forEach(item => { map[item.name] = (map[item.name] || 0) + (parseFloat(item.quantity) || 0); }); }); return Object.entries(map).sort((a,b)=>b[1]-a[1]).slice(0, 5); } const retailTop = top5(transfers); const wsTop = top5(wsTransfers); const colors = isDark ? ['#e94560', '#0f3460', '#533483', '#f39422', '#16c79a'] : ['#1a237e', '#283593', '#3949ab', '#5c6bc0', '#7986cb']; const wsColors = isDark ? ['#16c79a', '#0f3460', '#f39422', '#e94560', '#533483'] : ['#00695c', '#00897b', '#26a69a', '#4db6ac', '#80cbc4']; const topCtx = document.getElementById('topProductsChart'); if (topCtx) { if (window.topProductsChartInstance) window.topProductsChartInstance.destroy(); window.topProductsChartInstance = new Chart(topCtx, { type: 'doughnut', data: { labels: retailTop.map(p => String(p[0]).substring(0, 14)), datasets: [{ data: retailTop.map(p => p[1]), backgroundColor: colors, borderColor: isDark ? '#16213e' : '#fff', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: textColor, font: { size: 11 } } } } } }); } const topWsCtx = document.getElementById('topWsProductsChart'); if (topWsCtx) { if (window.topWsProductsChartInstance) window.topWsProductsChartInstance.destroy(); window.topWsProductsChartInstance = new Chart(topWsCtx, { type: 'doughnut', data: { labels: wsTop.map(p => String(p[0]).substring(0, 14)), datasets: [{ data: wsTop.map(p => p[1]), backgroundColor: wsColors, borderColor: isDark ? '#16213e' : '#fff', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: textColor, font: { size: 11 } } } } } }); } } function initCharts() { // Legacy function - charts are now rendered in loadOverview renderOverviewCharts(); }
🛒
طلب جديد وارد!
عميل جديد أرسل طلباً